package data.datas;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import network.e2e.FrameReader;
import network.e2e.FrameWriter;
import stalker.Stalker;
import data.Data;
import data.DataParser;
import data.content.Content;

public class CompleteHistoryDatas extends Datas {
  
  private boolean CHECK_REP_ON = true;
  
  /**
   * The main mutable data structure
   * <p>
   * Rep invariant: sorted according to creation time (last one = newest)
   */
  private final ArrayList<Data> datas_;
  private final Object DATAS_LOCK = new Object();
  
  private void checkRep() {
    assert (datas_ != null) :
      "Datas: fields must not be null";
    
    if (!CHECK_REP_ON) { // switch to turn off expensive checking
      return;
    }
    // expensive checking, O(N)
    for (int i = 1; i < datas_.size(); ++i) {
      assert (datas_.get(i-1).getCreationCalendar().compareTo(
          datas_.get(i).getCreationCalendar()) <= 0) :
        "Datas: elements in datas_ must be sorted according to creation time";
    }
  }
  
  /**
   * Creates an empty collection of datas
   */
  public CompleteHistoryDatas() {
    datas_ = new ArrayList<Data>();
    assert (datas_.size() == 0);
    checkRep();
  }
  
  /**
   * Reads a datas from the net and create it
   * @throws IOException 
   * @requires reader not null
   */
  public CompleteHistoryDatas(FrameReader reader) throws IOException {
    this();
    assert (reader != null);
    
    long count = reader.readLong();
    for (int i = 0; i < count; ++i) {
      this.insertData(DataParser.retrieve(reader));
    }
    checkRep();
  }
  
  /**
   * Mutate this by appending a data to the end of this collection
   * @requires all parameters not null
   */
  @Override
  public void addData(Stalker creator, Content content) {
    assert (creator != null && content != null);
    this.insertData(new Data(creator, content));
    checkRep();
  }
  
  /**
   * Retrieves the newest <code>retrieve_count</code> datas of this collection
   * <p>
   * If <code>retrieve_count</code> is greater than the number of datas
   * in this collection this returns all of them instead.
   * @requires retrieve_count not null, not negative
   * @param retrieve_count  the maximum number of datas to be retrieved
   * @return A list containing the elements in order from oldest to newest
   * @complexity O(<code>retrieve_count</code>)
   */
  public List<Data> retrieveLatestDatas(int retrieve_count) {
    assert (retrieve_count >= 0);
    synchronized (DATAS_LOCK) {
      return datas_.subList(Math.max(0,datas_.size() - retrieve_count), datas_.size());
    }
  }
  
  /**
   * Retrieves the latest data in this collection
   * @requires collection is not empty
   * @return The last data added to the collection
   */
  @Override
  public Data retrieveLatestData() {
    assert (!this.isEmpty());
    synchronized (DATAS_LOCK) {
      return datas_.get(datas_.size() - 1);
    }
  }
  
  /**
   * Retrieves the i-th data (0 is the first) from this collection
   * @requires index not negative, index is in range
   * @return the data in the corresponding index
   */
  public Data retrieveDataAt(long index) {
    assert (index >= 0 && index < this.size());
    synchronized (DATAS_LOCK) {
      return datas_.get((int)index);
    }
  }
  
  /**
   * Appends a data to the appropriate location
   * @requires data not null
   * @param data the data to be inserted
   */
  @Override
  public void insertData(Data data) {
    synchronized (DATAS_LOCK) {
      for (int i = 0; i < size(); ++i) {
        if (retrieveDataAt(i).equals(data)) {
          return;
        }
      }
      datas_.add(data);
    }
  }
  
  /**
   * Checks whether the collection contains no data
   * @return true iff the collection contains no data
   */
  @Override
  public boolean isEmpty() {
    synchronized (DATAS_LOCK) {
      return datas_.isEmpty();
    }
  }
  
  /**
   * Retrieves the total number of datas stored
   * @return the number of datas stored
   */
  public long size() {
    synchronized (DATAS_LOCK) {
      return datas_.size();
    }
  }
  
  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof CompleteHistoryDatas)) {
      return false;
    }
    
    CompleteHistoryDatas that = (CompleteHistoryDatas)obj;
    
    synchronized (DATAS_LOCK) {
      if (this.size() != that.size()) {
        return false;
      }
      
      for (int i = 0; i < this.size(); ++i) {
        if (this.retrieveDataAt(i).equals(that.retrieveDataAt(i))) {
          continue;
        } else {
          return false;
        }
      }
    }
    
    return true;
  }
  
  /**
   * Sent this through the net
   * @requires writer not null
   * @param writer the net
   */
  @Override
  public void send(FrameWriter writer) throws IOException {
    assert writer != null;
    writer.writeLong(size());
    for (int i = 0; i < size(); ++i) {
      DataParser.send(retrieveDataAt(i), writer);
    }
  }
  
  /**
   * Tests for membership
   * @requires Data not null
   * @param data data to be tested
   * @return true iff data is contained
   */
  public boolean contains(Data data) {
    assert (data != null);
    synchronized (DATAS_LOCK) {
      for (int i = 0; i < size(); ++i) {
        if (retrieveDataAt(i).equals(data)) {
          return true;
        }
      }
    }
    return false;
  }

  @Override
  public List<Data> retrieveAllDatas() {
    return retrieveLatestDatas((int)size());
  }
  
}
